/**
 * @fileoverview the plugin for less to support custom alias
 * @author houquan | 870301137@qq.com
 * @version 1.0.0 | 2020-03-15 | houquan      // initial version
 */

import path from "node:path";
import { normalizePath } from "./utils.js";
import { type Logger } from "./utils.js";

interface Options {
  prefix?: string;
  alias: Record<string, string | string[]>;
  logger?: Logger;
}

const defaultLogger = {
  log: console.log,
  error: console.error,
};

export default class LessAliasPlugin {
  constructor(private options: Options) {}

  install(less: LessStatic, pluginManager: Less.PluginManager) {
    const { prefix = "~", alias, logger = defaultLogger } = this.options;

    function resolve(filename: string) {
      if (filename.startsWith(prefix)) {
        const chunks = filename.split("/");
        const aliasKey = chunks[0].slice(1);
        const restPath = chunks.slice(1).join("/");
        const resolvedAlias = alias[aliasKey];

        let resolvedPath: string | undefined;
        if (Array.isArray(resolvedAlias)) {
          for (let i = 0, len = resolvedAlias.length; i < len; i++) {
            resolvedPath = normalizePath(path.join(resolvedAlias[i], restPath));
            if (resolvedPath) {
              return resolvedPath;
            }
          }
        } else {
          resolvedPath = normalizePath(path.join(resolvedAlias, restPath));
        }
        if (!resolvedPath) {
          throw new Error(`Invalid alias config for key: ${aliasKey}`);
        }
        return resolvedPath;
      }

      return filename;
    }

    class AliasPlugin extends less.FileManager {
      supports(filename: string, currentDirectory: string) {
        const aliasNames = Object.keys(alias);
        const len = aliasNames.length;

        for (let i = 0; i < len; i++) {
          const key = `${prefix}${aliasNames[i]}`;
          if (filename.includes(key) || currentDirectory.includes(key)) {
            return true;
          }
        }
        return false;
      }

      supportsSync(filename: string, currentDirectory: string) {
        return this.supports(filename, currentDirectory);
      }

      loadFile(
        filename: string,
        currentDirectory: string,
        options: Record<string, unknown>,
        environment: Less.Environment,
      ) {
        let resolved;
        try {
          resolved = resolve(filename);
        } catch (error) {
          logger.error(error);
        }
        if (!resolved) {
          const error = new Error(
            `[less-plugin-alias]: '${filename}' not found.`,
          );
          logger.error(error);
          throw error;
        }
        return super.loadFile(resolved, currentDirectory, options, environment);
      }

      loadFileSync(
        filename: string,
        currentDirectory: string,
        options: Record<string, unknown>,
        environment: Less.Environment,
      ) {
        let resolved;
        try {
          resolved = resolve(filename);
        } catch (error) {
          logger.error(error);
        }
        if (!resolved) {
          const error = new Error(
            `[less-plugin-alias]: '${filename}' not found.`,
          );
          logger.error(error);
          throw error;
        }
        return super.loadFileSync(
          resolved,
          currentDirectory,
          options,
          environment,
          // callback,
        );
      }
    }

    pluginManager.addFileManager(new AliasPlugin());
  }
}
